﻿/***************************************************************
*       
* add by OceanHo 2015/8/24 14:36:07
*
****************************************************************/

using oceanho.webapi.factory.httpCore;
using oceanho.webapi.factory.httpEntity;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Web;

namespace oceanho.webapi.factory.httpUtility
{
    /// <summary>
    /// 定义一个文件上传工具服务类
    /// </summary>
    internal sealed class FileUploadUtility
    {
        #region -- 上传辅助函数 --

        /// <summary>
        /// 保存上传文件到磁盘目录（此方法在http_service_handler的Upload中在文件类型校验合法后被调用）
        /// </summary>
        /// <param name="param">上传参数配置</param>
        /// <param name="fileHash">上传文件的物理路径Hashtable</param>
        internal static void SaveToDisk(UploadParameter param, System.Collections.Hashtable fileHash)
        {
            int cur_build = 0;      // 创建文件保存路径次数            
            string _filePath = string.Empty;

            if (!Directory.Exists(param.SaveToDir))
            {
                if (!param.AutoMKDir)
                    throw new APIFW_ArgumentException(" 上传目录不存在，请手动创建目录或将AutoMKDir参数设置为True ");
                fileHash.Add(-1, Directory.CreateDirectory(param.SaveToDir).FullName);
            }

            int size = 0;
            long completed = 0;
            byte[] buffer = new byte[param.BufferSize];

            for (int i = 0; i < param.FileUploads.Length; i++)
            {
                cur_build = 0;      // 上传文件名称重命名计数
                completed = 0;      // 当前上传文件已保存大小

                AdjustUploadItemFileName(param, ref param.FileUploads[i], ref cur_build, ref _filePath);

                if (param.FileUploads[i].IsValid && param.FileUploads[i].FileName != null &&
                    param.FileUploads[i].ContentLength > 0 && param.FileUploads[i].FileName.Length > 0)
                {
                    try
                    {
                        // 开始上传
                        param.ExcuteOnUploadItemStart(param.FileUploads[i]);

                        // request.Files.Get(param.FileUploads[i].ClientKey).SaveAs(_filePath);
                        using (FileStream fs = new FileStream(_filePath, FileMode.Create, FileAccess.ReadWrite))
                        {
                            using (BinaryWriter bw = new BinaryWriter(fs))
                            {
                                using (BinaryReader br = new BinaryReader(param.FileUploads[i].InputStream))
                                {
                                    while ((size = br.Read(buffer, 0, buffer.Length)) > 0)
                                    {
                                        completed += size;
                                        bw.Write(buffer, 0, size);

                                        // 进度变化
                                        param.ExcuteOnUploadItemProgressChange(param.FileUploads[i], completed);
                                    }
                                }
                            }
                        }
                        // 上传完成
                        param.ExcuteOnUploadItemCompleted(param.FileUploads[i]);
                    }
                    catch (Exception ex)
                    {
                        // 上传出错
                        if (!param.ExcuteOnUploadItemFailedHandler(param.FileUploads[i], ex)) { throw ex; }
                    }
                }
            }
        }


        /// <summary>
        /// 对已经上传的文件进行调整管理（删除。保留），当上传失败，发生异常的时候调用此方法
        /// </summary>
        /// <param name="arg">上传配置参数</param>
        /// <param name="_fileHash">已经上传的文件名hash表</param>
        internal static void DoTranstion(UploadParameter arg, Hashtable _fileHash)
        {
            if (arg.IsUseTranstion)
            {
                // 删除已经上传文件
                for (int i = 0; i < _fileHash.Count; i++)
                {
                    try
                    {
                        File.Delete(_fileHash[i].ToString());
                    }
                    catch { }
                }

                // 删除本次创建的目录
                try
                {
                    if (_fileHash.ContainsKey(-1))
                    {
                        // 防止误删（因为可能同时有多个用户上传文件,并且上传到同一个
                        // 目录中，A成功,B失败，目录是B创建的，这里就不能这样删了，尽管会提示不可以删除非空目录）
                        if (Directory.GetFiles(_fileHash[-1].ToString()).Length == 0)
                        {
                            File.Delete(_fileHash[-1].ToString());
                        }
                    }
                }
                catch { }
            }
        }

        /// <summary>
        /// 验证是否所有上传文件的文件类型，上传大小都符合
        /// 上传配置参数（都满足[即可以上传]返回true，否则返回false）
        /// </summary>
        /// <param name="uploadParam">上传参数对象</param>
        /// <returns></returns>
        internal static bool IsValid(UploadParameter uploadParam)
        {
            // 验证是上传参数数据正确性 --> 验证上传文件类型的正确性
            return !(FileUploadUtility.IsValidUploadBasicData(uploadParam))
            ? false : FileUploadUtility.IsValidExistUnAllowType(uploadParam);
        }


        /// <summary>
        /// 验证上传文件的：基础数据/验证模式与文件类型/所有文件总大小/单个文件大小 是否合法
        /// </summary>
        /// <param name="uploadParam">上传参数对象</param>
        /// <returns></returns>
        private static bool IsValidUploadBasicData(UploadParameter uploadParam)
        {
            // 基础数据必须正确
            if (uploadParam != null && uploadParam.FileUploads != null && uploadParam.FileUploads.Length > 0)
            {
                // 验证总文件大小
                if (uploadParam.AllowFileSize < uploadParam.FileTotalSize)
                {
                    throw new APIFW_ArgumentException("上传文件总大小超过了配置参数最大限制！");
                }

                // 验证单个文件大小
                foreach (UploadItem item in uploadParam.FileUploads)
                {
                    if (uploadParam.AllowSingleFileSize < item.ContentLength)
                    {
                        if (!uploadParam.ExcuteOnUploadItemValidFailed(item, FileUploadTypeValidOption.FileSize))
                        {
                            throw new APIFW_ArgumentException("上传文件[" + item.FileName + "]的大小超过了配置参数最大大小限制！");
                        }
                    }
                }

                // 验证模式为FileUploadTypeValidMode.AllowType，必须指定。UnOrAllowTypes参数
                if ((uploadParam.TypeValidMode == FileUploadTypeValidMode.AllowType))
                {
                    if (uploadParam.FileTypes == null || uploadParam.FileTypes.Count == 0)
                    {
                        throw new APIFW_ArgumentException("上传允许文件类型验证模式为 FileUploadTypeValidMode.AllowType,最少必须包含一项UnOrAllowTypes！");
                    }
                }
                return true;
            }
            throw new APIFW_ArgumentException(" 上传参数[UploadParameter]验证失败,没有任何允许上传的文件或者上传文件大小为零,请确认！");
        }


        /// <summary>
        /// 判断是否上传文件的文件类型（是否存在不存在与允许上传文件类型配置项中）
        /// </summary>
        /// <param name="uploadParam">上传参数对象</param>
        /// <returns></returns>
        private static bool IsValidExistUnAllowType(UploadParameter uploadParam)
        {
            int okQu = 0;

            // 文件类型的验证模式为 UnAllowType,如果uploadParam.UnOrAllowTypes为null或者Count为0,表示允许所有文件类型的文件上传
            if (uploadParam.TypeValidMode == httpCore.FileUploadTypeValidMode.UnAllowType)
            {
                if (uploadParam.FileTypes == null || uploadParam.FileTypes.Count == 0)
                {
                    return false;
                }
            }

            //
            // 校验方式为只允许，就遍历所有的文件，只需要找到一个文件的类型不存在于UnOrAllowTypes
            // 上传的文件类型数组中，就说明本次上传文件中包含非法的文件类型，调用ExcuteOnUploadItemValidFailed
            // -------------------------------------------------------------------------------------------//
            if (uploadParam.TypeValidMode == httpCore.FileUploadTypeValidMode.AllowType)
            {
                for (int a = 0; a < uploadParam.FileUploads.Length; a++)
                {
                    for (int b = 0; b < uploadParam.FileTypes.Count; b++)
                    {
                        // 此文件类型存在于UnOrAllowTypes中
                        // 说明它合法,break 继续下一个文件的文件类型验证
                        if (IsValidFileTypeWhenEqual(uploadParam, uploadParam.FileUploads[a], uploadParam.FileTypes[b]))
                        {
                            okQu++;
                            break;
                        }

                        // 循环到最后一个了。如果最后一个文件类型都不一样，那就说明这个文件类型非法
                        if (b == uploadParam.FileTypes.Count - 1)
                        {
                            if (IsinValidFileTypeWhenNotEqual(uploadParam, uploadParam.FileUploads[a], uploadParam.FileTypes[b]))
                            {
                                okQu = -1;
                                break;
                            }
                        }
                    }

                    // 找到了一个非法类型的上传文件，并且
                    // ExcuteOnUploadItemValidFailed方法返回False，表示不能继续上传了。
                    if (okQu == -1) { break; }
                }
                return uploadParam.FileUploads.Length == okQu;
            }


            //
            // 校验方式为不允许，就遍历所有的文件，只需要找到一个文件的类型存在于UnOrAllowTypes
            // 上传的文件类型数组中，就说明本次上传文件中包含不允许上传的文件类型，调用ExcuteOnUploadItemValidFailed
            // -------------------------------------------------------------------------------------------------------//
            for (int a = 0; a < uploadParam.FileUploads.Length; a++)
            {
                for (int b = 0; b < uploadParam.FileTypes.Count; b++)
                {
                    if (IsinValidFileTypeWhenEqual(uploadParam, uploadParam.FileUploads[a], uploadParam.FileTypes[b]))
                    {
                        okQu = -1;
                        break;
                    }
                }
                if (okQu == -1) { return false; }
            }
            return true;
        }

        /// <summary>
        /// 尝试对长传文件进行保存路径与文件名称调整（该方法可能会递归调用自己，但不会出现死循环
        /// 有最大次数限制,如果达到最大调整次数还没有成功，会抛出 APIFW_InvalidOperationException 异常）
        /// </summary>
        /// <param name="param">上传配置参数</param>
        /// <param name="item">当前执行上传文件对象</param>
        /// <param name="callCount">对路径进行调整的次数</param>
        /// <param name="fileNewPath">文件的上传保存路径</param>
        private static void AdjustUploadItemFileName(UploadParameter param, ref UploadItem item, ref int callCount, ref string fileNewPath)
        {
            fileNewPath = Path.Combine(param.SaveToDir, item.FileName);
            if (File.Exists(fileNewPath))
            {
                SFUEDoOption(param.FileExistDoOption, param.SaveToDir, ref item);

                if (callCount++ > FileUploadDefault.ReUploadItemNameMaxCount)
                    throw new APIFW_InvalidOperationException("上传文件名称冲突,程序尝试了[" + FileUploadDefault.ReUploadItemNameMaxCount + "]次重命名任然失败,请重新尝试");

                FileUploadUtility.AdjustUploadItemFileName(param, ref item, ref callCount, ref fileNewPath);
            }
        }
        /// <summary>
        /// 辅助函数（对 FileUploadExistDoOption 进行分解处理，switch FileUploadExistDoOption -> SFUEDoPtion）
        /// </summary>
        /// <param name="doOption">FileUploadExistDoOption对象</param>
        /// <param name="fileDir">文件保存的目录（不是文件保存路径，是保存目录。比如 X:\\www\\root\\）</param>
        /// <param name="upload">当前上传文件信息描述对象</param>
        private static void SFUEDoOption(FileUploadExistDoOption doOption, string fileDir, ref UploadItem upload)
        {
            string _str = string.Empty;
            switch (doOption)
            {
                case FileUploadExistDoOption.ThrowEx:
                    throw new APIFW_InvalidOperationException("上传文件[" + upload.FileName + "]已经存在服务器上");

                case FileUploadExistDoOption.AutoNewName:
                    upload.FileName = "RN_" + MD5Utility.Encrypt(Guid.NewGuid().ToString(), MD5Mode.Default) + "_" + upload.FileName;
                    break;

                case FileUploadExistDoOption.BakAndUpload:
                    _str = "BAK_" + DateTime.Now.ToString("yyyyMMdd") +
                        MD5Utility.Encrypt(Guid.NewGuid().ToString(), MD5Mode.Default) + "_";

                    File.Move(Path.Combine(fileDir, upload.FileName), Path.Combine(fileDir, _str + upload.FileName));
                    break;

                default:
                    break;
            }
        }


        /// <summary>
        /// 验证文件类型是否有效（当文件类型与参数fileSuffix一样时，表示有效）
        /// 如果当文件类型与参数fileSuffix不一样时，通过调用 uploadParam.ExcuteOnUploadItemValidFailed来确定文件是否有效
        /// </summary>
        /// <returns></returns>
        private static bool IsValidFileTypeWhenEqual(UploadParameter uploadParam, UploadItem uploadItem, string fileSuffix)
        {
            if (fileSuffix.Equals(
                uploadItem.FileSuffix, StringComparison.OrdinalIgnoreCase))
            {
                return uploadItem.IsValid = true;
            }

            uploadItem.IsValid = false;
            uploadItem.InValidMessages.Add(" 文件 [" + uploadItem.FileName + "] 上传类型验证无效 ");

            // 调用ExcuteOnUploadItemValidFailed进行（如果返回false，表示不允许忽略错误，禁止继续上传文件）
            return uploadItem.IsValid = uploadParam.ExcuteOnUploadItemValidFailed(uploadItem, FileUploadTypeValidOption.FileType);
        }


        /// <summary>
        /// 验证文件类型是否无效（当文件类型与参数fileSuffix一样时，表示有效）
        /// 如果当文件类型与参数fileSuffix不一样时，通过调用 uploadParam.ExcuteOnUploadItemValidFailed来确定文件是否有效
        /// </summary>
        /// <returns></returns>
        private static bool IsinValidFileTypeWhenEqual(UploadParameter uploadParam, UploadItem uploadItem, string fileSuffix)
        {
            if (!fileSuffix.Equals(
                uploadItem.FileSuffix, StringComparison.OrdinalIgnoreCase))
            {
                return !(uploadItem.IsValid = true);
            }

            uploadItem.IsValid = false;
            uploadItem.InValidMessages.Add(" 文件 [" + uploadItem.FileName + "] 上传类型验证无效 ");

            // 调用ExcuteOnUploadItemValidFailed进行（如果返回false，表示不允许忽略错误，禁止继续上传文件）
            return !(uploadItem.IsValid = uploadParam.ExcuteOnUploadItemValidFailed(uploadItem, FileUploadTypeValidOption.FileType));
        }

        /// <summary>
        /// 验证文件类型是否有效（当文件类型与参数fileSuffix一样时，表示有效）
        /// 如果当文件类型与参数fileSuffix不一样时，通过调用 uploadParam.ExcuteOnUploadItemValidFailed来确定文件是否有效
        /// </summary>
        /// <returns></returns>
        private static bool IsValidFileTypeWhenNotEqual(UploadParameter uploadParam, UploadItem uploadItem, string fileSuffix)
        {
            if (!fileSuffix.Equals(
                uploadItem.FileSuffix, StringComparison.OrdinalIgnoreCase))
            {
                return uploadItem.IsValid = true;
            }

            uploadItem.IsValid = false;
            uploadItem.InValidMessages.Add(" 文件 [" + uploadItem.FileName + "] 上传类型验证无效 ");

            // 调用ExcuteOnUploadItemValidFailed进行（如果返回false，表示不允许忽略错误，禁止继续上传文件）
            return uploadItem.IsValid = uploadParam.ExcuteOnUploadItemValidFailed(uploadItem, FileUploadTypeValidOption.FileType);
        }


        /// <summary>
        /// 验证文件类型是否无效（当文件类型与参数fileSuffix一样时，表示有效）
        /// 如果当文件类型与参数fileSuffix不一样时，通过调用 uploadParam.ExcuteOnUploadItemValidFailed来确定文件是否有效
        /// </summary>
        /// <returns></returns>
        private static bool IsinValidFileTypeWhenNotEqual(UploadParameter uploadParam, UploadItem uploadItem, string fileSuffix)
        {
            if (fileSuffix.Equals(
                uploadItem.FileSuffix, StringComparison.OrdinalIgnoreCase))
            {
                return !(uploadItem.IsValid = true);
            }

            uploadItem.IsValid = false;
            uploadItem.InValidMessages.Add(" 文件 [" + uploadItem.FileName + "] 上传类型验证无效 ");

            // 调用ExcuteOnUploadItemValidFailed进行（如果返回false，表示不允许忽略错误，禁止继续上传文件）
            return !(uploadItem.IsValid = uploadParam.ExcuteOnUploadItemValidFailed(uploadItem, FileUploadTypeValidOption.FileType));
        }
        #endregion

        #region -- 辅助类方法 -> BuildUploadParameter/BuildDownloadParameter --

        /// <summary>
        /// 创建上传文件的配置参数对象
        /// </summary>
        /// <param name="request">HttpRequest</param>
        /// <param name="server">HttpServerUtility</param>
        /// <returns></returns>
        internal static UploadParameter BuildUploadParameter(HttpRequest request, HttpServerUtility server)
        {
            long totalSize = 0;
            List<UploadItem> uploadItems = new List<UploadItem>();

            for (int i = 0; i < request.Files.Count; i++)
            {
                if (request.Files[i].ContentLength > 0)
                {
                    totalSize += request.Files[i].ContentLength;
                    uploadItems.Add(
                        new UploadItem(
                            request.Files.AllKeys[i],
                            request.Files[i].FileName,
                            request.Files[i].ContentType,
                            request.Files[i].ContentLength,
                            request.Files[i].InputStream
                     ));
                }
            }

            // 参数配置
            UploadParameter uploadParam = new UploadParameter(uploadItems.ToArray(),
                server.MapPath("~/App_Data/Upload/" + DateTime.Now.ToString("yyyyMMddHH") + "/"), totalSize);

            // 上传事件
            uploadParam.OnUploadItemStart += UploadParam_OnUploadItemStart;
            uploadParam.OnUploadItemCompleted += upload_OnUploadItemCompleted;
            uploadParam.OnUploadItemFailedHandler += upload_OnUploadItemFailedHandler;
            uploadParam.OnUploadItemProgressChange += upload_OnUploadItemProgressChange;
            uploadParam.OnUploadItemValidFailed += UploadParam_OnUploadItemValidFailed;

            return uploadParam;
        }

        private static bool UploadParam_OnUploadItemValidFailed(UploadItem upload, FileUploadTypeValidOption validOption)
        {
            return false;
        }

        private static void UploadParam_OnUploadItemStart(UploadItem upload)
        {

        }

        private static void upload_OnUploadItemProgressChange(UploadItem upload, long progress)
        {
        }

        private static void upload_OnUploadItemCompleted(UploadItem upload)
        {
        }

        private static void upload_OnUploadItemFailedHandler(UploadItem upload, Exception fail)
        {
            throw fail;
        }

        /// <summary>
        /// 创建下载文件的配置参数对象
        /// </summary>
        /// <param name="request">HttpRequest</param>
        /// <param name="server">HttpServerUtility</param>
        /// <returns></returns>
        internal static DownloadParameter BuildDownlodParameter(HttpRequest request, HttpServerUtility server)
        {
            return default(DownloadParameter);
        }
        #endregion
    }
}
